/*******************************************************************************
 * Copyright (c) 2000, 2016 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import org.eclipse.core.commands.common.EventManager;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker;
import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IElementFactory;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IPersistableElement;
import org.eclipse.ui.IWorkbenchPreferenceConstants;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.IWorkingSetElementAdapter;
import org.eclipse.ui.IWorkingSetManager;
import org.eclipse.ui.IWorkingSetUpdater;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.dialogs.IWorkingSetEditWizard;
import org.eclipse.ui.dialogs.IWorkingSetNewWizard;
import org.eclipse.ui.dialogs.IWorkingSetPage;
import org.eclipse.ui.dialogs.IWorkingSetSelectionDialog;
import org.eclipse.ui.internal.dialogs.WorkingSetEditWizard;
import org.eclipse.ui.internal.dialogs.WorkingSetNewWizard;
import org.eclipse.ui.internal.dialogs.WorkingSetSelectionDialog;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
import org.eclipse.ui.internal.registry.WorkingSetDescriptor;
import org.eclipse.ui.internal.registry.WorkingSetRegistry;
import org.eclipse.ui.internal.util.PrefUtil;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.statushandlers.StatusManager;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;


/**
 * Abstract implementation of <code>IWorkingSetManager</code>.
 */
public abstract class AbstractWorkingSetManager extends EventManager implements
		IWorkingSetManager, BundleListener, IExtensionChangeHandler {

	static abstract class WorkingSetRunnable implements ISafeRunnable {

		@Override
		public void handleException(Throwable exception) {
			StatusManager.getManager().handle(
					StatusUtil.newStatus(PlatformUI.PLUGIN_ID, exception));
		}
	}

	private SortedSet<AbstractWorkingSet> workingSets = new TreeSet<>((o1, o2) -> o1.getUniqueId().compareTo(o2.getUniqueId()));

	private List<IWorkingSet> recentWorkingSets = new ArrayList<>();

	private BundleContext bundleContext;
	private Map<String, IWorkingSetUpdater> updaters = new HashMap<>();

	private Map<String, IWorkingSetElementAdapter> elementAdapters = new HashMap<>();

	private static final IWorkingSetUpdater NULL_UPDATER = new IWorkingSetUpdater() {
		@Override
		public void add(IWorkingSet workingSet) {
		}
		@Override
		public boolean remove(IWorkingSet workingSet) {
			return true;
		}
		@Override
		public boolean contains(IWorkingSet workingSet) {
			return true;
		}
		@Override
		public void dispose() {
		}
	};

	private static final IWorkingSetElementAdapter IDENTITY_ADAPTER = new IWorkingSetElementAdapter() {

		@Override
		public IAdaptable[] adaptElements(IWorkingSet ws, IAdaptable[] elements) {
			return elements;
		}

		@Override
		public void dispose() {
		}
	};

	/**
	 * Returns the descriptors for the given editable working set ids. If an id
	 * refers to a missing descriptor, or one that is non-editable, it is
	 * skipped. If <code>null</code> is passed, all editable descriptors are
	 * returned.
	 *
	 * @param supportedWorkingSetIds
	 *            the ids for the working set descriptors, or <code>null</code>
	 *            for all editable descriptors
	 * @return the descriptors corresponding to the given editable working set
	 *         ids
	 */
	private static WorkingSetDescriptor[] getSupportedEditableDescriptors(String[] supportedWorkingSetIds) {
		WorkingSetRegistry registry = WorkbenchPlugin.getDefault().getWorkingSetRegistry();
		if (supportedWorkingSetIds == null) {
			return registry.getNewPageWorkingSetDescriptors();
		}
		List<WorkingSetDescriptor> result = new ArrayList<>(supportedWorkingSetIds.length);
		for (String supportedWorkingSetId : supportedWorkingSetIds) {
			WorkingSetDescriptor desc = registry.getWorkingSetDescriptor(supportedWorkingSetId);
			if (desc != null && desc.isEditable()) {
				result.add(desc);
			}
		}
		return result.toArray(new WorkingSetDescriptor[result.size()]);
	}

    protected AbstractWorkingSetManager(BundleContext context) {
    	bundleContext= context;
    	bundleContext.addBundleListener(this);
    	PlatformUI.getWorkbench().getExtensionTracker().registerHandler(this, ExtensionTracker
				.createExtensionPointFilter(getExtensionPointFilter()));
	}

	/**
	 * Returns the working sets extension point.
	 *
	 * @return the working sets extension point
	 * @since 3.3
	 */
	private IExtensionPoint getExtensionPointFilter() {
        // RAP [bm]: namespace
        return Platform.getExtensionRegistry().getExtensionPoint(
                PlatformUI.PLUGIN_EXTENSION_NAME_SPACE,
                IWorkbenchRegistryConstants.PL_WORKINGSETS);
	}

	@Override
	public void dispose() {
		bundleContext.removeBundleListener(this);
		for (IWorkingSetUpdater next : updaters.values()) {
			SafeRunner.run(new WorkingSetRunnable() {
				@Override
				public void run() throws Exception {
					next.dispose();
				}
			});
		}

		for (IWorkingSetElementAdapter next : elementAdapters.values()) {
			SafeRunner.run(new WorkingSetRunnable() {
				@Override
				public void run() throws Exception {
					next.dispose();
				}
			});
		}
	}

    //---- working set creation -----------------------------------------------------

    @Override
	public IWorkingSet createWorkingSet(String name, IAdaptable[] elements) {
        return new WorkingSet(name, name, elements);
    }

    @Override
	public IWorkingSet createAggregateWorkingSet(String name, String label,
			IWorkingSet[] components) {
		return new AggregateWorkingSet(name, label, components);
	}

    @Override
	public IWorkingSet createWorkingSet(IMemento memento) {
        return restoreWorkingSet(memento);
    }

    //---- working set management ---------------------------------------------------

    @Override
	public void addWorkingSet(IWorkingSet workingSet) {
		IWorkingSet wSet = getWorkingSet(workingSet.getName());
    	Assert.isTrue(wSet==null,"working set with same name already registered"); //$NON-NLS-1$
        internalAddWorkingSet(workingSet);
    }

    private void internalAddWorkingSet(IWorkingSet workingSet) {
		AbstractWorkingSet abstractWorkingSet = (AbstractWorkingSet) workingSet;
		workingSets.add(abstractWorkingSet);
		abstractWorkingSet.connect(this);
		addToUpdater(workingSet);
		firePropertyChange(CHANGE_WORKING_SET_ADD, null, workingSet);
	}

    protected boolean internalRemoveWorkingSet(IWorkingSet workingSet) {
        boolean workingSetRemoved = workingSets.remove(workingSet);
        boolean recentWorkingSetRemoved = recentWorkingSets.remove(workingSet);

        if (workingSetRemoved) {
        	((AbstractWorkingSet)workingSet).disconnect();
        	removeFromUpdater(workingSet);
            firePropertyChange(CHANGE_WORKING_SET_REMOVE, workingSet, null);
        }
        return workingSetRemoved || recentWorkingSetRemoved;
    }

	@Override
	public IWorkingSet[] getWorkingSets() {
		SortedSet<IWorkingSet> visibleSubset = new TreeSet<IWorkingSet>(WorkingSetComparator.getInstance());
		for (IWorkingSet workingSet : workingSets) {
			if (workingSet.isVisible()) {
				visibleSubset.add(workingSet);
			}
		}
		return visibleSubset.toArray(new IWorkingSet[visibleSubset.size()]);
	}

	@Override
	public IWorkingSet[] getAllWorkingSets() {
		IWorkingSet[] sets = workingSets.toArray(new IWorkingSet[workingSets.size()]);
		Arrays.sort(sets, WorkingSetComparator.getInstance());
		return sets;
	}

	@Override
	public IWorkingSet getWorkingSet(String name) {
		if (name == null || workingSets == null) {
			return null;
		}

		for (IWorkingSet workingSet : workingSets) {
			if (name.equals(workingSet.getName())) {
				return workingSet;
			}
		}

		return null;
	}

    // ---- recent working set management --------------------------------------

    @Override
	public IWorkingSet[] getRecentWorkingSets() {
        return recentWorkingSets.toArray(new IWorkingSet[recentWorkingSets.size()]);
    }

    /**
     * Adds the specified working set to the list of recently used
     * working sets.
     *
     * @param workingSet working set to added to the list of recently
     * 	used working sets.
     */
    protected void internalAddRecentWorkingSet(IWorkingSet workingSet) {
    		if (!workingSet.isVisible()) {
				return;
			}
        recentWorkingSets.remove(workingSet);
        recentWorkingSets.add(0, workingSet);
		sizeRecentWorkingSets();
    }

    //---- equals and hash code -----------------------------------------------

    /**
     * Tests the receiver and the object for equality
     *
     * @param object object to compare the receiver to
     * @return true=the object equals the receiver, it has the same
     * 	working sets. false otherwise
     */
    @Override
	public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (!getClass().getName().equals(object.getClass().getName())) {
			return false;
		}
        AbstractWorkingSetManager other= (AbstractWorkingSetManager)object;
        return other.workingSets.equals(workingSets);
    }

    /**
     * Returns the hash code.
     *
     * @return the hash code.
     */
    @Override
	public int hashCode() {
        return workingSets.hashCode();
    }

    //---- property listeners -------------------------------------------------

    @Override
	public void addPropertyChangeListener(IPropertyChangeListener listener) {
        addListenerObject(listener);
    }

    @Override
	public void removePropertyChangeListener(IPropertyChangeListener listener) {
        removeListenerObject(listener);
    }

    /**
     * Notify property change listeners about a change to the list of
     * working sets.
     *
     * @param changeId one of
     * 	IWorkingSetManager#CHANGE_WORKING_SET_ADD
     * 	IWorkingSetManager#CHANGE_WORKING_SET_REMOVE
     * 	IWorkingSetManager#CHANGE_WORKING_SET_CONTENT_CHANGE
     * 	IWorkingSetManager#CHANGE_WORKING_SET_NAME_CHANGE
     * @param oldValue the removed working set or null if a working set
     * 	was added or changed.
     * @param newValue the new or changed working set or null if a working
     * 	set was removed.
     */
    protected void firePropertyChange(String changeId, Object oldValue,
            Object newValue) {
        final Object[] listeners = getListeners();

        if (listeners.length == 0) {
			return;
		}

        final PropertyChangeEvent event = new PropertyChangeEvent(this,
                changeId, oldValue, newValue);
		Runnable notifier = () -> {
			for (Object listener : listeners) {
				final IPropertyChangeListener propertyChangeListener = (IPropertyChangeListener) listener;
				ISafeRunnable safetyWrapper = new ISafeRunnable() {

					@Override
					public void run() throws Exception {
						propertyChangeListener.propertyChange(event);
					}

					@Override
					public void handleException(Throwable exception) {
						// logged by the runner
					}
				};
				SafeRunner.run(safetyWrapper);
			}
		};
		// Notifications are sent on the UI thread.
		if (Display.getCurrent() != null) {
			notifier.run();
		} else {
			// Use an asyncExec to avoid deadlocks.
			Display.getDefault().asyncExec(notifier);
		}
	}

	/**
	 * Fires a property change event for the changed working set. Should only be
	 * called by org.eclipse.ui.internal.WorkingSet.
	 *
	 * @param changedWorkingSet
	 *            the working set that has changed
	 * @param propertyChangeId
	 *            the changed property. one of
	 *            CHANGE_WORKING_SET_CONTENT_CHANGE,
	 *            CHANGE_WORKING_SET_LABEL_CHANGE, and
	 *            CHANGE_WORKING_SET_NAME_CHANGE
	 * @param oldValue
	 *            the old value
	 */
	public void workingSetChanged(IWorkingSet changedWorkingSet,
			String propertyChangeId, Object oldValue) {
		firePropertyChange(propertyChangeId, oldValue, changedWorkingSet);
	}

    // ---- Persistence
	// ----------------------------------------------------------------

    /**
     * Saves all persistable working sets in the persistence store.
     *
     * @param memento the persistence store
     * @see IPersistableElement
     */
    public void saveWorkingSetState(IMemento memento) {
		Iterator<AbstractWorkingSet> iterator = workingSets.iterator();

        // break the sets into aggregates and non aggregates.  The aggregates should be saved after the non-aggregates
        // so that on restoration all necessary aggregate components can be found.

		ArrayList<IWorkingSet> standardSets = new ArrayList<>();
		ArrayList<IWorkingSet> aggregateSets = new ArrayList<>();
		while (iterator.hasNext()) {
			IWorkingSet set = iterator.next();
			if (set instanceof AggregateWorkingSet) {
				aggregateSets.add(set);
			} else {
				standardSets.add(set);
			}
		}

        saveWorkingSetState(memento, standardSets);
        saveWorkingSetState(memento, aggregateSets);
    }

	/**
	 * @param memento the memento to save to
	 * @param list the working sets to save
	 * @since 3.2
	 */
	private void saveWorkingSetState(final IMemento memento, List list) {
		for (Iterator i = list.iterator(); i.hasNext();) {
            final IPersistableElement persistable = (IWorkingSet) i.next();
			SafeRunner.run(new WorkingSetRunnable() {

				@Override
				public void run() throws Exception {
					// create a dummy node to write too - the write could fail so we
					// shouldn't soil the final memento until we're sure it succeeds.
					XMLMemento dummy = XMLMemento.createWriteRoot(IWorkbenchConstants.TAG_WORKING_SET);
					dummy.putString(IWorkbenchConstants.TAG_FACTORY_ID,
							persistable.getFactoryId());
					persistable.saveState(dummy);

					// if the dummy was created successfully copy it to the real output
					IMemento workingSetMemento = memento
							.createChild(IWorkbenchConstants.TAG_WORKING_SET);
					workingSetMemento.putMemento(dummy);
				}
			});

        }
	}

    /**
     * Recreates all working sets from the persistence store
     * and adds them to the receiver.
     *
     * @param memento the persistence store
     */
	protected void restoreWorkingSetState(IMemento memento) {
		for (IMemento child : memento.getChildren(IWorkbenchConstants.TAG_WORKING_SET)) {
			AbstractWorkingSet workingSet = (AbstractWorkingSet) restoreWorkingSet(child);
			if (workingSet != null) {
				internalAddWorkingSet(workingSet);
			}
		}
	}

    /**
     * Recreates a working set from the persistence store.
     *
     * @param memento the persistence store
     * @return the working set created from the memento or null if
     * 	creation failed.
     */
    protected IWorkingSet restoreWorkingSet(final IMemento memento) {
        String factoryID = memento
                .getString(IWorkbenchConstants.TAG_FACTORY_ID);

        if (factoryID == null) {
            // if the factory id was not set in the memento
            // then assume that the memento was created using
            // IMemento.saveState, and should be restored using WorkingSetFactory
            factoryID = AbstractWorkingSet.FACTORY_ID;
        }
        final IElementFactory factory = PlatformUI.getWorkbench().getElementFactory(
                factoryID);
        if (factory == null) {
            WorkbenchPlugin
                    .log("Unable to restore working set - cannot instantiate factory: " + factoryID); //$NON-NLS-1$
            return null;
        }
		final IAdaptable[] adaptable = new IAdaptable[1];
		SafeRunner.run(new WorkingSetRunnable() {

			@Override
			public void run() throws Exception {
				adaptable[0] = factory.createElement(memento);
			}
		});
        if (adaptable[0] == null) {
            WorkbenchPlugin
                    .log("Unable to restore working set - cannot instantiate working set: " + factoryID); //$NON-NLS-1$
            return null;
        }
        if ((adaptable[0] instanceof IWorkingSet) == false) {
            WorkbenchPlugin
                    .log("Unable to restore working set - element is not an IWorkingSet: " + factoryID); //$NON-NLS-1$
            return null;
        }
        return (IWorkingSet) adaptable[0];
    }

    /**
     * Saves the list of most recently used working sets in the persistence
     * store.
     *
     * @param memento the persistence store
     */
    protected void saveMruList(IMemento memento) {
        Iterator iterator = recentWorkingSets.iterator();

        while (iterator.hasNext()) {
            IWorkingSet workingSet = (IWorkingSet) iterator.next();
            IMemento mruMemento = memento
                    .createChild(IWorkbenchConstants.TAG_MRU_LIST);

            mruMemento.putString(IWorkbenchConstants.TAG_NAME, workingSet
                    .getName());
        }
    }

   /**
     * Restores the list of most recently used working sets from the
     * persistence store.
     *
     * @param memento the persistence store
     */
    protected void restoreMruList(IMemento memento) {
        IMemento[] mruWorkingSets = memento
                .getChildren(IWorkbenchConstants.TAG_MRU_LIST);

        for (int i = mruWorkingSets.length - 1; i >= 0; i--) {
            String workingSetName = mruWorkingSets[i]
                    .getString(IWorkbenchConstants.TAG_NAME);
            if (workingSetName != null) {
                IWorkingSet workingSet = getWorkingSet(workingSetName);
                if (workingSet != null) {
                    internalAddRecentWorkingSet(workingSet);
                }
            }
        }
    }

    //---- user interface support -----------------------------------------------------

    /**
     * @see org.eclipse.ui.IWorkingSetManager#createWorkingSetEditWizard(org.eclipse.ui.IWorkingSet)
     * @since 2.1
     */
    @Override
	public IWorkingSetEditWizard createWorkingSetEditWizard(
            IWorkingSet workingSet) {
        String editPageId = workingSet.getId();
        WorkingSetRegistry registry = WorkbenchPlugin.getDefault()
                .getWorkingSetRegistry();
        IWorkingSetPage editPage = null;

        if (editPageId != null) {
            editPage = registry.getWorkingSetPage(editPageId);
        }

        // the following block kind of defeats IWorkingSet.isEditable() and it
		// doesn't make sense for there to be a default page in such a case.

	     if (editPage == null) {
			editPage = registry.getDefaultWorkingSetPage();
			if (editPage == null) {
				return null;
			}
		}

        WorkingSetEditWizard editWizard = new WorkingSetEditWizard(editPage);
        editWizard.setSelection(workingSet);
        return editWizard;
    }

    /**
     * @deprecated use createWorkingSetSelectionDialog(parent, true) instead
     */
    @Deprecated
	@Override
	public IWorkingSetSelectionDialog createWorkingSetSelectionDialog(
            Shell parent) {
        return createWorkingSetSelectionDialog(parent, true);
    }

    @Override
	public IWorkingSetSelectionDialog createWorkingSetSelectionDialog(
            Shell parent, boolean multi) {
        return createWorkingSetSelectionDialog(parent, multi, null);
    }

    /**
	 * {@inheritDoc}
	 */
    @Override
	public IWorkingSetNewWizard createWorkingSetNewWizard(String[] workingSetIds) {
         WorkingSetDescriptor[] descriptors= getSupportedEditableDescriptors(workingSetIds);
         if (descriptors.length == 0) {
			return null;
		}
         return new WorkingSetNewWizard(descriptors);
}

    //---- working set delta handling -------------------------------------------------

	@Override
	public void bundleChanged(BundleEvent event) {
		String symbolicName = event.getBundle().getSymbolicName();
		if (symbolicName == null)
			return;
		// If the workbench isn't running anymore simply return.
		if (!PlatformUI.isWorkbenchRunning()) {
			return;
		}

		if (event.getBundle().getState() == Bundle.ACTIVE) {
			final WorkingSetDescriptor[] descriptors = WorkbenchPlugin.getDefault()
					.getWorkingSetRegistry().getUpdaterDescriptorsForNamespace(
							symbolicName);

			Job job = new WorkbenchJob(
					NLS
							.bind(
									WorkbenchMessages.get().AbstractWorkingSetManager_updatersActivating,
									symbolicName)) {

				@Override
				public IStatus runInUIThread(IProgressMonitor monitor) {
					synchronized (updaters) {
						for (WorkingSetDescriptor descriptor : descriptors) {
							List workingSets = getWorkingSetsForId(descriptor
									.getId());
							if (workingSets.size() == 0) {
								continue;
							}
							final IWorkingSetUpdater updater = getUpdater(descriptor);
							for (Iterator iter = workingSets.iterator(); iter
									.hasNext();) {
								final IWorkingSet workingSet = (IWorkingSet) iter
										.next();
								SafeRunner.run(new WorkingSetRunnable() {

									@Override
									public void run() throws Exception {
										if (!updater.contains(workingSet)) {
											updater.add(workingSet);
										}
									}
								});
							}
						}
					}
					return Status.OK_STATUS;
				}
			};
			job.setSystem(true);
			job.schedule();
		}
	}

	private List getWorkingSetsForId(String id) {
		List result= new ArrayList();
		for (IWorkingSet ws : workingSets) {
    		if (id.equals(ws.getId())) {
				result.add(ws);
			}
		}
    	return result;
	}

    private void addToUpdater(final IWorkingSet workingSet) {
    	WorkingSetDescriptor descriptor= WorkbenchPlugin.getDefault()
			.getWorkingSetRegistry().getWorkingSetDescriptor(workingSet.getId());
    	if (descriptor == null || !descriptor.isUpdaterClassLoaded()) {
			return;
		}
		synchronized(updaters) {
	    	final IWorkingSetUpdater updater= getUpdater(descriptor);
	    	SafeRunner.run(new WorkingSetRunnable() {

				@Override
				public void run() throws Exception {
					if (!updater.contains(workingSet)) {
						updater.add(workingSet);
					}
				}});
		}
    }

	private IWorkingSetUpdater getUpdater(WorkingSetDescriptor descriptor) {
		IWorkingSetUpdater updater = updaters.get(descriptor.getId());
		if (updater == null) {
			updater = descriptor.createWorkingSetUpdater();
			if (updater == null) {
				updater = NULL_UPDATER;
			} else {
				firePropertyChange(CHANGE_WORKING_SET_UPDATER_INSTALLED, null, updater);
				PlatformUI.getWorkbench().getExtensionTracker().registerObject(
						descriptor.getConfigurationElement().getDeclaringExtension(), updater,
						IExtensionTracker.REF_WEAK);

			}
			updaters.put(descriptor.getId(), updater);
		}
		return updater;
	}

	IWorkingSetElementAdapter getElementAdapter(WorkingSetDescriptor descriptor) {
		IWorkingSetElementAdapter elementAdapter = elementAdapters.get(descriptor.getId());
		if (elementAdapter == null) {
			elementAdapter = descriptor.createWorkingSetElementAdapter();
			if (elementAdapter == null) {
				elementAdapter = IDENTITY_ADAPTER;
			} else {
				elementAdapters.put(descriptor.getId(), elementAdapter);
			}
		}
		return elementAdapter;
	}

	private void removeFromUpdater(final IWorkingSet workingSet) {
		synchronized (updaters) {
			final IWorkingSetUpdater updater = updaters.get(workingSet.getId());
			if (updater != null) {
				SafeRunner.run(new WorkingSetRunnable() {

					@Override
					public void run() throws Exception {
						updater.remove(workingSet);
					}
				});
			}
		}
	}

    @Override
	public IWorkingSetSelectionDialog createWorkingSetSelectionDialog(Shell parent, boolean multi, String[] workingsSetIds) {
        return new WorkingSetSelectionDialog(parent, multi, workingsSetIds);
    }

	/**
	 * Save the state to the state file.
	 *
	 * @param stateFile
	 * @throws IOException
	 */
	public void saveState(File stateFile) throws IOException {
		XMLMemento memento = XMLMemento
				.createWriteRoot(IWorkbenchConstants.TAG_WORKING_SET_MANAGER);
		saveWorkingSetState(memento);
		saveMruList(memento);

		FileOutputStream stream = new FileOutputStream(stateFile);
		OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
		memento.save(writer);
		writer.close();

	}

	@Override
	public void addExtension(IExtensionTracker tracker, IExtension extension) {
		// nothing - this is handled lazily.  These items are only created as needed by the getUpdater() and getElementAdapter() methods

	}

	@Override
	public void removeExtension(IExtension extension, Object[] objects) {
		for (Object object : objects) {
			if (object instanceof IWorkingSetUpdater) {
				removeUpdater((IWorkingSetUpdater)object);

			}
			if (object instanceof IWorkingSetElementAdapter) {
				removeElementAdapter((IWorkingSetElementAdapter) object);
			}
		}
	}

	/**
	 * Remove the element adapter from the manager and dispose of it.
	 *
	 * @param elementAdapter
	 * @since 3.3
	 */
	private void removeElementAdapter(
			final IWorkingSetElementAdapter elementAdapter) {
		SafeRunner.run(new WorkingSetRunnable() {

			@Override
			public void run() throws Exception {
				elementAdapter.dispose();

			}
		});
		synchronized (elementAdapters) {
			elementAdapters.values().remove(elementAdapter);
		}
	}

	/**
	 * Remove the updater from the manager and dispose of it.
	 *
	 * @param updater
	 * @since 3.3
	 */
	private void removeUpdater(final IWorkingSetUpdater updater) {
		SafeRunner.run(new WorkingSetRunnable() {

			@Override
			public void run() throws Exception {
				updater.dispose();

			}
		});
		synchronized (updaters) {
			updaters.values().remove(updater);
		}
		firePropertyChange(IWorkingSetManager.CHANGE_WORKING_SET_UPDATER_UNINSTALLED, updater, null);
	}

	@Override
	public void addToWorkingSets(final IAdaptable element, IWorkingSet[] workingSets) {
		// ideally this method would be in a static util class of some kind but
		// we dont have any such beast for working sets and making one for one
		// method is overkill.
		for (final IWorkingSet workingSet : workingSets) {
			SafeRunner.run(new WorkingSetRunnable() {

				@Override
				public void run() throws Exception {
					IAdaptable[] adaptedNewElements = workingSet
							.adaptElements(new IAdaptable[] { element });
					if (adaptedNewElements.length == 1) {
						IAdaptable[] elements = workingSet.getElements();
						IAdaptable[] newElements = new IAdaptable[elements.length + 1];
						System.arraycopy(elements, 0, newElements, 0,
								elements.length);
						newElements[newElements.length - 1] = adaptedNewElements[0];
						workingSet.setElements(newElements);
					}
				}});
		}
	}

	@Override
	public void setRecentWorkingSetsLength(int length) {
		if (length < 1 || length > 99)
			throw new IllegalArgumentException("Invalid recent working sets length: " + length); //$NON-NLS-1$
		IPreferenceStore store = PrefUtil.getAPIPreferenceStore();
		store.setValue(IWorkbenchPreferenceConstants.RECENTLY_USED_WORKINGSETS_SIZE, length);
		// adjust length
		sizeRecentWorkingSets();
	}

	private void sizeRecentWorkingSets() {
		int maxLength = getRecentWorkingSetsLength();
		while (recentWorkingSets.size() > maxLength) {
			int lastPosition = recentWorkingSets.size() - 1;
			recentWorkingSets.remove(lastPosition);
		}
	}

	@Override
	public int getRecentWorkingSetsLength() {
		IPreferenceStore store = PrefUtil.getAPIPreferenceStore();
		return store.getInt(IWorkbenchPreferenceConstants.RECENTLY_USED_WORKINGSETS_SIZE);
	}
}
